package com.showtao.front.service;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.showtao.common.enumeration.EErrorCode;
import com.showtao.common.exception.BizServiceException;
import com.showtao.front.domain.VehicleBrand;
import com.showtao.front.domain.VehicleLine;
import com.showtao.front.domain.VehicleModel;
import com.showtao.front.domain.VehicleModelFeature;
import com.showtao.front.dto.LabelItemDto;
import com.showtao.front.dto.VehicleLineDto;
import com.showtao.front.dto.VehicleModelDto;
import com.showtao.front.dto.upstream.VehicleBrandSavingDto;
import com.showtao.front.dto.upstream.VehicleLineSavingDto;
import com.showtao.front.dto.upstream.VehicleModelFeatureSavingDto;
import com.showtao.front.dto.upstream.VehicleModelSavingDto;
import com.showtao.front.repository.VehicleBrandRepository;
import com.showtao.front.repository.VehicleLineRepository;
import com.showtao.front.repository.VehicleModelFeatureRepository;
import com.showtao.front.repository.VehicleModelRepository;

@Service
public class VehicleService {

    @Autowired
    private VehicleModelRepository vehicleModelRepository;
    @Autowired
    private VehicleLineRepository vehicleLineRepository;
    @Autowired
    private VehicleBrandRepository vehicleBrandRepository;
    @Autowired
    private VehicleModelFeatureRepository vehicleModelFeatureRepository;

    @Transactional(readOnly = true)
    @Cacheable(value = "allVehicleBrandMap")
    public Map<String, List<VehicleBrand>> findAllVehicleBrandMap() {
        Map<String, List<VehicleBrand>> map = new TreeMap<String, List<VehicleBrand>>();
        List<VehicleBrand> brands = vehicleBrandRepository.findAll();
        for (VehicleBrand brand : brands) {
            if (!StringUtils.isBlank(brand.getAlphabet())) {
                // touch to get associations
                List<VehicleLine> lines = brand.getLines();
                for (VehicleLine line : lines) {
                    line.getModels();
                }
                List<VehicleBrand> alphaBrands = map.get(brand.getAlphabet());
                if (alphaBrands == null) {
                    alphaBrands = new ArrayList<VehicleBrand>();
                    map.put(brand.getAlphabet(), alphaBrands);
                }
                alphaBrands.add(brand);
            }
        }
        return map;
    }

    @Transactional(readOnly = true)
    @Cacheable(value = "allVehicleLineMap")
    public Map<String, List<VehicleLine>> findAllVehicleLineMap() {
        Map<String, List<VehicleLine>> map = new TreeMap<String, List<VehicleLine>>();
        List<VehicleBrand> brands = vehicleBrandRepository.findAllJoinLines();
        for (VehicleBrand brand : brands) {
            if (!StringUtils.isBlank(brand.getName())) {
                map.put(brand.getName(), new ArrayList<VehicleLine>(brand.getLines()));
            }
        }
        return map;
    }

    @Transactional(readOnly = true)
    @Cacheable(value = "allVehicleModelMap")
    public Map<String, List<VehicleModel>> findAllVehicleModelMap() {
        Map<String, List<VehicleModel>> map = new TreeMap<String, List<VehicleModel>>();
        List<VehicleLine> lines = vehicleLineRepository.findAllJoinModels();
        for (VehicleLine line : lines) {
            if (!StringUtils.isBlank(line.getName())) {
                map.put(line.getName(), new ArrayList<VehicleModel>(line.getModels()));
            }
        }
        return map;
    }

    @Transactional(readOnly = true)
    @Cacheable(value = "vehicleBreadCrumb", key = "'MODEL-' + #modelId")
    public List<LabelItemDto> getVehicleBreadCrumb(Long modelId) {
        List<LabelItemDto> dtos = new ArrayList<LabelItemDto>();
        VehicleModel model = vehicleModelRepository.getOneJoinLine(modelId);
        VehicleLine line = vehicleLineRepository.getOneJoinVehicleBrand(model.getLine().getId());
        VehicleBrand brand = line.getBrand();
        LabelItemDto dto = new LabelItemDto();
        dto.setId(brand.getId());
        dto.setLabel(brand.getName());
        dtos.add(dto);
        dto = new LabelItemDto();
        dto.setId(line.getId());
        dto.setLabel(line.getName());
        dtos.add(dto);
        dto = new LabelItemDto();
        dto.setId(model.getId());
        dto.setLabel(model.getName());
        dtos.add(dto);
        return dtos;
    }

    @Transactional(readOnly = true)
    public List<VehicleModel> findLimitedVehicleModelAccordingTo(String keywords, int size) {
        Pageable pageable = new PageRequest(0, size);
        Page<VehicleModel> vms = vehicleModelRepository.findByNameLike(keywords, pageable);
        return vms.getContent();
    }

    @Transactional(readOnly = true)
    public Page<VehicleBrand> findVehicleBrands(Pageable pageable) {
        return vehicleBrandRepository.findAll(pageable);
    }

    @Transactional
    @CacheEvict(value = "allVehicleBrandMap")
    public void deleteVehicleBrand(Long id) {
        vehicleBrandRepository.delete(id);
    }

    @Transactional
    @CacheEvict(value = "allVehicleBrandMap")
    public void saveVehicleBrand(VehicleBrandSavingDto dto) {
        VehicleBrand entity = null;
        if (dto.getId() != null) {
            entity = vehicleBrandRepository.getOne(dto.getId());
            if (entity == null) {
                throw new BizServiceException(EErrorCode.COMM_INTERNAL_ERROR, "找不到ID=" + dto.getId()
                        + "的VehicleBrand实体");
            }
        } else {
            entity = new VehicleBrand();
            entity.setCreatedAt(Calendar.getInstance().getTime());
        }
        entity.setAlphabet(StringUtils.trim(dto.getAlphabet()));
        entity.setName(StringUtils.trim(dto.getName()));
        entity.setUpdatedAt(Calendar.getInstance().getTime());
        vehicleBrandRepository.save(entity);
    }

    @Transactional(readOnly = true)
    public Page<VehicleLineDto> findVehicleLines(Pageable pageable) {
        Page<VehicleLine> page = vehicleLineRepository.findAllJoinVehicleBrand(pageable);
        List<VehicleLineDto> list = new ArrayList<VehicleLineDto>();
        for (VehicleLine vl : page) {
            list.add(VehicleLineDto.from(vl));
        }
        return new PageImpl<VehicleLineDto>(list, pageable, page.getTotalElements());
    }

    @Transactional
    @CacheEvict(value = { "allVehicleBrandMap", "allVehicleLineMap" })
    public void deleteVehicleLine(Long id) {
        VehicleLine vl = vehicleLineRepository.findOne(id);
        VehicleBrand vb = vl.getBrand();
        vb.getLines().remove(vl);
    }

    @Transactional
    @CacheEvict(value = { "allVehicleBrandMap", "allVehicleLineMap" })
    public void saveVehicleLine(VehicleLineSavingDto dto) {
        VehicleLine entity = null;
        VehicleBrand brand = null;
        if (dto.getId() != null) {
            entity = vehicleLineRepository.getOneJoinVehicleBrand(dto.getId());
            if (entity == null) {
                throw new BizServiceException(EErrorCode.COMM_INTERNAL_ERROR, "找不到ID=" + dto.getId()
                        + "的VehicleBrand实体");
            }
        } else {
            entity = new VehicleLine();
            entity.setCreatedAt(Calendar.getInstance().getTime());
        }
        if (dto.getBrandId() != null) {
            brand = vehicleBrandRepository.findOne(dto.getBrandId());
            if (brand == null) {
                throw new BizServiceException(EErrorCode.COMM_INTERNAL_ERROR, "找不到ID=" + dto.getBrandId()
                        + "的VehicleLine实体");
            }
        }
        entity.setBrand(brand);
        entity.setName(StringUtils.trim(dto.getName()));
        entity.setUpdatedAt(Calendar.getInstance().getTime());
        vehicleLineRepository.save(entity);
    }

    @Transactional(readOnly = true)
    public Page<VehicleModelDto> findVehicleModels(Pageable pageable) {
        Page<VehicleModel> page = vehicleModelRepository.findAllJoinLineAndFeatures(pageable);
        List<VehicleModelDto> list = new ArrayList<VehicleModelDto>();
        for (VehicleModel vm : page) {
            list.add(VehicleModelDto.from(vm));
        }
        return new PageImpl<VehicleModelDto>(list, pageable, page.getTotalElements());
    }

    @Transactional
    @CacheEvict(value = { "allVehicleBrandMap", "allVehicleLineMap", "allVehicleModelMap" })
    public void deleteVehicleModel(Long id) {
        VehicleModel vm = vehicleModelRepository.findOne(id);
        VehicleLine vl = vm.getLine();
        vl.getModels().remove(vm);
    }

    @SuppressWarnings("unchecked")
    @Transactional
    @CacheEvict(value = { "allVehicleBrandMap", "allVehicleLineMap", "allVehicleModelMap" })
    public void saveVehicleModel(VehicleModelSavingDto dto) {
        VehicleModel entity = null;
        VehicleLine line = null;
        if (dto.getId() != null) {
            entity = vehicleModelRepository.getOneJoinLineAndFeatures(dto.getId());
            if (entity == null) {
                throw new BizServiceException(EErrorCode.COMM_INTERNAL_ERROR, "找不到ID=" + dto.getId()
                        + "的VehicleBrand实体");
            }
        } else {
            entity = new VehicleModel();
            entity.setCreatedAt(Calendar.getInstance().getTime());
        }
        if (dto.getLineId() != null) {
            line = vehicleLineRepository.findOne(dto.getLineId());
            if (line == null) {
                throw new BizServiceException(EErrorCode.COMM_INTERNAL_ERROR, "找不到ID=" + dto.getLineId()
                        + "的VehicleLine实体");
            }
        }
        entity.setIsHatchback(dto.getIsHatchback());
        entity.setLine(line);
        entity.setName(StringUtils.trim(dto.getName()));
        entity.setUpdatedAt(Calendar.getInstance().getTime());
        vehicleModelRepository.save(entity);

        // Make sure no NullPointerException
        if (dto.getFeatures() == null) {
            dto.setFeatures(new ArrayList<VehicleModelFeatureSavingDto>());
        }
        if (entity.getFeatures() == null) {
            entity.setFeatures(new ArrayList<VehicleModelFeature>());
        }
        Collection<VehicleModelFeatureSavingDto> exists = VehicleModelFeatureSavingDto.from(entity.getFeatures());
        Collection<VehicleModelFeatureSavingDto> toBeAdded = new ArrayList<VehicleModelFeatureSavingDto>();
        Collection<VehicleModelFeatureSavingDto> toBeUpdated = new ArrayList<VehicleModelFeatureSavingDto>();
        for (VehicleModelFeatureSavingDto d : dto.getFeatures()) {
            if (d.getId() == null) {
                toBeAdded.add(d);
            } else if (d.isChanged()) {
                toBeUpdated.add(d);
            }
        }
        Collection<VehicleModelFeatureSavingDto> notChanged = CollectionUtils.subtract(
                CollectionUtils.intersection(exists, dto.getFeatures()), toBeUpdated);

        Collection<VehicleModelFeature> toBeAddedEntities = new ArrayList<VehicleModelFeature>();
        Collection<VehicleModelFeature> toBeUpdatedEntities = new ArrayList<VehicleModelFeature>();
        Collection<VehicleModelFeature> notChangedEntities = new ArrayList<VehicleModelFeature>();

        // Add VMFs
        if (toBeAdded != null && toBeAdded.size() != 0) {
            for (VehicleModelFeatureSavingDto vmfDto : toBeAdded) {
                VehicleModelFeature vmf = new VehicleModelFeature();
                vmf.setName(vmfDto.getName());
                vmf.setCategoryNumber(vmfDto.getCategoryNumber());
                vmf.setCreatedAt(Calendar.getInstance().getTime());
                vmf.setModel(entity);
                toBeAddedEntities.add(vmf);
            }
            vehicleModelFeatureRepository.save(toBeAddedEntities);
        }

        // Update VMFs
        if (toBeUpdated != null && toBeUpdated.size() != 0) {
            Map<Long, VehicleModelFeatureSavingDto> map = new HashMap<Long, VehicleModelFeatureSavingDto>();
            for (VehicleModelFeatureSavingDto vmfDto : toBeUpdated) {
                map.put(vmfDto.getId(), vmfDto);
            }
            if (map.entrySet().size() != 0) {
                Collection<VehicleModelFeature> dbEntities = vehicleModelFeatureRepository.findByIdIn(map.keySet());
                for (VehicleModelFeature vmf : dbEntities) {
                    VehicleModelFeatureSavingDto vmfDto = map.get(vmf.getId());
                    if (vmfDto != null) {
                        vmf.setName(vmfDto.getName());
                        vmf.setCategoryNumber(vmfDto.getCategoryNumber());
                        vmf.setUpdatedAt(Calendar.getInstance().getTime());
                        vmf.setModel(entity);
                        toBeUpdatedEntities.add(vmf);
                    }
                }
            }
        }

        if (notChanged != null && notChanged.size() != 0) {
            Collection<Long> ids = CollectionUtils.collect(notChanged, new Transformer() {
                @Override
                public Object transform(Object input) {
                    VehicleModelFeatureSavingDto dto = (VehicleModelFeatureSavingDto) input;
                    return dto.getId();
                }
            });
            notChangedEntities = vehicleModelFeatureRepository.findByIdIn(ids);
        }

        entity.getFeatures().clear();
        entity.getFeatures().addAll(toBeAddedEntities);
        entity.getFeatures().addAll(toBeUpdatedEntities);
        entity.getFeatures().addAll(notChangedEntities);
    }

    @Transactional(readOnly = true)
    @Cacheable(value = "modelFeatureMap", key = "'MODEL-' + #modelId")
    public Map<String, List<VehicleModelFeature>> getModelFeatureMap(Long modelId) {
        Map<String, List<VehicleModelFeature>> map = new TreeMap<String, List<VehicleModelFeature>>();
        VehicleModel model = vehicleModelRepository.getOne(modelId);
        Set<VehicleModelFeature> vmfs = vehicleModelFeatureRepository.findByModel(model);
        for (VehicleModelFeature vmf : vmfs) {
            List<VehicleModelFeature> list = map.get(vmf.getCategoryNumber());
            if (list == null) {
                list = new ArrayList<VehicleModelFeature>();
                map.put(vmf.getCategoryNumber(), list);
            }
            list.add(vmf);
        }
        return map;
    }

    @Transactional(readOnly = true)
    public List<VehicleModel> getTop5VehicleModel() {
        Pageable pagable = new PageRequest(0, 5, new Sort(new Order(Direction.DESC, "viewCount")));
        Page<VehicleModel> page = vehicleModelRepository.findAll(pagable);
        return page.getContent();
    }

    @Transactional
    public void increaseViewCount(Long modelId) {
        VehicleModel vm = vehicleModelRepository.findOne(modelId);
        vm.setViewCount(vm.getViewCount() + 1);
        vehicleModelRepository.save(vm);
    }
    
    @Transactional(readOnly = true)
    public List<VehicleModelDto> findLimitedVehicleModelByKeywords(String keywords) {
        List<VehicleModel> results = vehicleModelRepository.findByNameLike(keywords);
        List<VehicleModelDto> dtos = new ArrayList<VehicleModelDto>();
        for(VehicleModel model : results){
        	VehicleModelDto dto = new VehicleModelDto();
        	dto.setId(model.getId());
        	dto.setName(model.getName());
        	dto.setLineName(model.getLine().getName());
        	dtos.add(dto);
        }
        return dtos;
    }
    
    @Transactional
    public VehicleModel findVehicleModelById(Long modelId) {
        VehicleModel vm = vehicleModelRepository.findOne(modelId);
        return vm;
    }
    
}
